package demo.abc;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import bsh.EvalError;
import bsh.Interpreter;

/**
 * 输入: 一个号码(数字)和一个特征字符串，其中特征字符串可以含有应文字母A-Z, a-z，数字0123...9，*和?号 <br/>
 * 输出: 号码与特征字符是否匹配 <br/>
 * 规则: 1) ?号固定占一个字符<br/>
 * 2) *号可以占任意个字符<br/>
 * 3) 数字直接就是数字，<br/>
 * 4) 大写字母则按照ABC...Z的顺序递加，(如:A为5，则B为6，C为7) <br/>
 * 5) 小写字母之间不设置关系，但是相同小写字母表示相同数字，(如a为5，b为7等) 举例: 12345与*ABC?可以匹配，但与*AB?C不能匹配<br/>
 * 6) 不同字母（大写字母或小写字母）表示不同数字<br/>
 * 思路: 对特征字符串逐个进行递归分析，每个取出来的字分4种情况，分别为*号，?号，字母，数字<br/>
 * 当*号时，若剩下的字符串没有*号，则*号所占位置已经固定，递归检测下个字符，
 * 若剩下字符串有*号，则枚举*号所占位置在0到剩下最多个位置之间，按照枚举的位置递归检测下个字符; <br/>
 * 当?号时，?固定占1个位置; 当英文时，按照英文字母在当前匹配字符串中所处位置对应号码串中的位置指向的数字进行比较，分两种情况<br/>
 * (1)此英文字母为整个特征字符串第一个英文字母，则它所对应的号码为以后字母的参照<br/>
 * (2)此英文字母不是整个特征字符串的第一个英文字母，则按照参照来检测当前字母对应是否正确， 若正确，则继续递归，否则返回上一层递归false;<br/>
 * 当数字时，按照数字字母在当前匹配字符串中所处位置对应号码串中的位置指向的数字进行比较， 正确则继续递归，否则返回上一层false;<br/>
 * 最后，若剩下的特征字符串为空，则表示特征字符串此种位置匹配通过，返回true;程序结束。 若没有达到上一条条件，则返回false;匹配失败，程序结束。
 */

public class TeleNumberABCExprMatcher {
    // First match letter of Not Available
    private final static int FIRST_MATCH_OFFSET = -999;
    // 字母与数字之间的映射关系
    private Map<Character, Character> mapped;
    // 电话号码字符串
    private String numStr;
    // 规则字符串
    private String specStr;
    // 条件字符串
    private String condExpr;

    /**
     * 根据匹配类型,号码,规则字符串判断号码是否匹配
     * 
     * @param matchType
     * @param numbStr
     * @param ruleStr
     * @return
     */
    public static boolean matches(String numbStr, String ruleStr) {
        TeleNumberABCExprMatcher matcher = new TeleNumberABCExprMatcher();
        return matcher.ruleMatches(numbStr, ruleStr);
    }

    /**
     * 号码字符串是否匹配规则字符串
     * 
     * @param numStr
     *            号码字符串， 例如 18602506990
     * @param ruleStr
     *            规则字符串，例如 186A25AabbA(a == 6 && b > 8)
     * @return true，匹配，false 不匹配
     */
    private boolean ruleMatches(String numStr, String ruleStr) {
        TeleNumberABCExprMatcher matcher = new TeleNumberABCExprMatcher();
        matcher.setNumStr(numStr);

        int posOfBrace = ruleStr.indexOf('(');
        if (posOfBrace < 0) {
            matcher.setSpecStr(ruleStr);
            matcher.setCondExpr(null);
        }
        else {
            matcher.setSpecStr(ruleStr.substring(0, posOfBrace));
            matcher.setCondExpr(ruleStr.substring(posOfBrace));
        }

        return matcher.matches();
    }

    /**
     * 号码字符串是否匹配规则字符串
     * 
     * @return true，匹配，false 不匹配
     */
    public boolean matches() {
        mapped = new HashMap<Character, Character>();
        if (!matches(0, 0, FIRST_MATCH_OFFSET)) {
            return false;
        }

        condExpr = condExpr != null ? condExpr.trim() : "";
        // 如果条件表达式不为空，则进行条件表达式的运算
        if (!condExpr.isEmpty()) {
            // 替换条件表达式中的字母为对应的数字
            for (Entry<Character, Character> entry : mapped.entrySet()) {
                condExpr = condExpr.replaceAll("" + entry.getKey(), "" + entry.getValue());
            }

            // 解析表达式的取值
            try {
                return (Boolean) new Interpreter().eval(condExpr);
            }
            catch (EvalError e) {
                e.printStackTrace();
                return false;
            }
        }

        return true;
    }

    // 主要匹配检测方法，递归调用
    // firstMatchOffset 首次大写字母匹配相对于字母'A'的偏移量
    private boolean matches(int posOfNum, int posOfSpec, int firstMatchAOffset) {
        // 若剩下的特征字符串为空，则表示匹配完毕
        if (posOfSpec >= specStr.length() && posOfNum >= numStr.length()) {
            return true;
        }

        // 最后规则是*号，直接返回true
        if ("*".equals(specStr.substring(posOfSpec))) {
            return true;
        }

        // 规则结束或者号码结束，直接返回false
        if (posOfSpec >= specStr.length() || posOfNum >= numStr.length()) {
            return false;
        }

        char specChar = specStr.charAt(posOfSpec);
        char num = numStr.charAt(posOfNum);

        if (specChar == '*') { // 如果检测字符为*
            return matches(posOfNum, posOfSpec + 1, firstMatchAOffset)
                    || matches(posOfNum + 1, posOfSpec, firstMatchAOffset);
        }

        if (specChar == '?') { // 如果检测字符为?
            return matches(posOfNum + 1, posOfSpec + 1, firstMatchAOffset);
        }

        if (specChar >= 'a' && specChar <= 'z') { // 如果检测字符为小写字母
            if (mapped.containsKey(specChar)) {
                if (num != mapped.get(specChar)) {
                    mapped.remove(specChar);
                    return false;
                }
            }
            else {
                // 检查是否与现有的映射相同取值
                for (Map.Entry<Character, Character> entry : mapped.entrySet()) {
                    if (entry.getValue().charValue() == num) {
                        return false;
                    }
                }
                mapped.put(specChar, num);
            }

            return matches(posOfNum + 1, posOfSpec + 1, firstMatchAOffset);
        }

        if (specChar >= 'A' && specChar <= 'Z') { // 如果检测字符为大写字母
            // 第一次匹配大写字母
            if (firstMatchAOffset == FIRST_MATCH_OFFSET) {
                mapped.put(specChar, num);
                return matches(posOfNum + 1, posOfSpec + 1, num - '0' - specChar + 'A');
            }
            // 比较字符内容与当前位置对应的数是否相符
            if (num - '0' == firstMatchAOffset + specChar - 'A') {
                mapped.put(specChar, num);
                return matches(posOfNum + 1, posOfSpec + 1, firstMatchAOffset);
            }

            return false;
        }

        if (specChar >= '0' && specChar <= '9') { // 如果检测字符为数字
            // 比较数字内容与当前位置对应的数是否相符
            return numStr.charAt(posOfNum) == specChar && matches(posOfNum + 1, posOfSpec + 1, firstMatchAOffset);
        }

        return false;
    }

    public String getNumStr() {
        return numStr;
    }

    public void setNumStr(String numStr) {
        this.numStr = numStr;
    }

    public String getSpecStr() {
        return specStr;
    }

    public void setSpecStr(String specStr) {
        this.specStr = specStr;
    }

    public String getCondExpr() {
        return condExpr;
    }

    public void setCondExpr(String condExpr) {
        this.condExpr = condExpr;
    }

    /**
     * 取得字母与数字之间的映射关系
     * 
     * @return
     */
    public Map<Character, Character> getMapped() {
        return mapped;
    }
}
